package org.andengine.opengl.util;

import android.os.Build;

import org.andengine.util.debug.Debug;
import org.andengine.util.system.SystemUtils;

import java.nio.ByteBuffer;

/**
 * (c) Zynga 2011
 *
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @see http://code.google.com/p/android/issues/detail?id=11078
 * @see http://code.google.com/p/android/issues/detail?id=16941
 * @since 23:06:51 - 11.08.2011
 */
public class BufferUtils {
    // ===========================================================
    // Constants
    // ===========================================================

    private static final boolean NATIVE_LIB_LOADED;

    /**
     * Android issue 16941.
     */
    private static final boolean WORKAROUND_BYTEBUFFER_PUT_FLOATARRAY;
    /**
     * Android issue 16941.
     */
    private static final boolean WORKAROUND_BYTEBUFFER_ALLOCATE_DIRECT;

    static {
        boolean loadLibrarySuccess;
        try {
            System.loadLibrary("andengine");
            loadLibrarySuccess = true;
        } catch (final UnsatisfiedLinkError pUnsatisfiedLinkError) {
            loadLibrarySuccess = false;
        }
        NATIVE_LIB_LOADED = loadLibrarySuccess;

        if (BufferUtils.NATIVE_LIB_LOADED) {
            if (SystemUtils.isAndroidVersion(Build.VERSION_CODES.HONEYCOMB, Build.VERSION_CODES.HONEYCOMB_MR2)) {
                WORKAROUND_BYTEBUFFER_ALLOCATE_DIRECT = true;
            } else {
                WORKAROUND_BYTEBUFFER_ALLOCATE_DIRECT = false;
            }

            if (SystemUtils.isAndroidVersionOrLower(Build.VERSION_CODES.FROYO)) {
                WORKAROUND_BYTEBUFFER_PUT_FLOATARRAY = true;
            } else {
                WORKAROUND_BYTEBUFFER_PUT_FLOATARRAY = false;
            }
        } else {
            WORKAROUND_BYTEBUFFER_ALLOCATE_DIRECT = false;
            if (SystemUtils.isAndroidVersion(Build.VERSION_CODES.HONEYCOMB, Build.VERSION_CODES.HONEYCOMB_MR2)) {
                Debug.w("Creating a " + ByteBuffer.class.getSimpleName() + " will actually allocate 4x the memory than requested!");
            }

            WORKAROUND_BYTEBUFFER_PUT_FLOATARRAY = false;
        }
    }

    // ===========================================================
    // Fields
    // ===========================================================

    // ===========================================================
    // Constructors
    // ===========================================================

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    // ===========================================================
    // Methods
    // ===========================================================

    /**
     * @param pCapacity the capacity of the returned {@link ByteBuffer} in bytes.
     * @return
     */
    public static ByteBuffer allocateDirectByteBuffer(final int pCapacity) {
        if (BufferUtils.WORKAROUND_BYTEBUFFER_ALLOCATE_DIRECT) {
            return BufferUtils.jniAllocateDirect(pCapacity);
        } else {
            return ByteBuffer.allocateDirect(pCapacity);
        }
    }

    public static void freeDirectByteBuffer(final ByteBuffer pByteBuffer) {
        if (BufferUtils.WORKAROUND_BYTEBUFFER_ALLOCATE_DIRECT) {
            BufferUtils.jniFreeDirect(pByteBuffer);
        }
    }

    private native static ByteBuffer jniAllocateDirect(final int pCapacity);

    private native static void jniFreeDirect(final ByteBuffer pByteBuffer);

    /**
     * @param pByteBuffer must be a direct Buffer.
     * @param pSource
     * @param pLength     to copy in pSource.
     * @param pOffset     in pSource.
     */
    public static void put(final ByteBuffer pByteBuffer, final float[] pSource, final int pLength, final int pOffset) {
        if (BufferUtils.WORKAROUND_BYTEBUFFER_PUT_FLOATARRAY) {
            BufferUtils.jniPut(pByteBuffer, pSource, pLength, pOffset);
        } else {
            for (int i = pOffset; i < (pOffset + pLength); i++) {
                pByteBuffer.putFloat(pSource[i]);
            }
        }
        pByteBuffer.position(0);
        pByteBuffer.limit(pLength << 2);
    }

    private native static void jniPut(final ByteBuffer pByteBuffer, final float[] pSource, final int pLength, final int pOffset);

    public static short getUnsignedByte(final ByteBuffer pByteBuffer) {
        return (short) (pByteBuffer.get() & 0xFF);
    }

    public static void putUnsignedByte(final ByteBuffer pByteBuffer, final int pValue) {
        pByteBuffer.put((byte) (pValue & 0xFF));
    }

    public static short getUnsignedByte(final ByteBuffer pByteBuffer, final int pPosition) {
        return (short) (pByteBuffer.get(pPosition) & (short) 0xFF);
    }

    public static void putUnsignedByte(final ByteBuffer pByteBuffer, final int pPosition, final int pValue) {
        pByteBuffer.put(pPosition, (byte) (pValue & 0xFF));
    }

    public static int getUnsignedShort(final ByteBuffer pByteBuffer) {
        return pByteBuffer.getShort() & 0xFFFF;
    }

    public static void putUnsignedShort(final ByteBuffer pByteBuffer, final int pValue) {
        pByteBuffer.putShort((short) (pValue & 0xFFFF));
    }

    public static int getUnsignedShort(final ByteBuffer pByteBuffer, final int pPosition) {
        return pByteBuffer.getShort(pPosition) & 0xFFFF;
    }

    public static void putUnsignedShort(final ByteBuffer pByteBuffer, final int pPosition, final int pValue) {
        pByteBuffer.putShort(pPosition, (short) (pValue & 0xFFFF));
    }

    public static long getUnsignedInt(final ByteBuffer pByteBuffer) {
        return pByteBuffer.getInt() & 0xFFFFFFFFL;
    }

    public static void putUnsignedInt(final ByteBuffer pByteBuffer, final long pValue) {
        pByteBuffer.putInt((int) (pValue & 0xFFFFFFFFL));
    }

    public static long getUnsignedInt(final ByteBuffer pByteBuffer, final int pPosition) {
        return pByteBuffer.getInt(pPosition) & 0xFFFFFFFFL;
    }

    public static void putUnsignedInt(final ByteBuffer pByteBuffer, final int pPosition, final long pValue) {
        pByteBuffer.putInt(pPosition, (short) (pValue & 0xFFFFFFFFL));
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
}
